package com.sgcc.lms.handwrite.mvc.servlet;

import com.sgcc.lms.handwrite.mvc.annotations.Controller;
import com.sgcc.lms.handwrite.mvc.annotations.Qualifier;
import com.sgcc.lms.handwrite.mvc.annotations.RequestMapping;
import com.sgcc.lms.handwrite.mvc.annotations.Service;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author: 李孟帅
 * @CreateTime: 2020-05-14 14:50
 * @Description: ${Description}
 */
@WebServlet(name = "dispatcherServlet",urlPatterns = "/",loadOnStartup = 1,
         initParams = {@WebInitParam(name = "base-package",value = "com.sgcc.lms.handwrite.mvc")})
public class DispatcherServlet extends HttpServlet {
    //扫描的基包
    private String basePackage="";
    //基包下所有的带包路径全限定类名
    private List<String> packageNames=new ArrayList<>();
    //实例化对象
    private Map<String, Object> instanceMap=new HashMap<>();
    //注解上的名称
    private Map<String, String> nameMap =new HashMap<>();
    //映射关系
    private Map<String, Method> urlMethodMap =new HashMap<>();
    //Method和全限定类名的映射关系
    private Map<Method,String> methodPackageMap=new HashMap<>();

    @Override
    public void init(ServletConfig config) throws ServletException {
        basePackage = config.getInitParameter("base-package");
        System.out.println(basePackage);
        try {
            System.out.println("----------------------------------");
            //扫描包得到全部的带包路径全限定名
            scanBasePackage(basePackage);
            //把带有@Controller/@Service注解的类实例放入map中。key为注解上的名字
            instance(packageNames);
            //ioc注入
            IOC();
            //完成地址和方法的映射
            handlerUrlMethodMap();
        }catch ( Exception e){

        }
    }

    private void scanBasePackage(String basePackage) {
        URL url = this.getClass().getClassLoader().getResource(basePackage.replaceAll("\\.", "/"));
        System.out.println(url);
        File basePackageFile = new File(url.getPath());
        System.out.println("scan:"+basePackageFile);
        File[] files = basePackageFile.listFiles();
        for (File file : files) {
            if (file.isDirectory()){
                scanBasePackage(basePackage+"."+file.getName());
            }else if (file.isFile()){
                packageNames.add(basePackage+"."+file.getName().split("\\.")[0]);
            }
        }
        System.out.println(packageNames);
    }

    private void instance(List<String> packageNames) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        if (packageNames.size()<1){
            return;
        }
        for (String packageName : packageNames) {
            Class<?> c = Class.forName(packageName);
            if (c.isAnnotationPresent(Controller.class)){
                Controller controller = c.getAnnotation(Controller.class);
                String value = controller.value();
                instanceMap.put(value,c.newInstance());
                nameMap.put(packageName,value);
                System.out.println("Controller:"+packageName+",value:"+value);
            }else if (c.isAnnotationPresent(Service.class)){
                Service service = c.getAnnotation(Service.class);
                String value = service.value();
                instanceMap.put(value,c.newInstance());
                nameMap.put(packageName,value);
                System.out.println("Service:"+packageName+",value:"+value);
            }
        }
    }

    private void IOC() throws IllegalAccessException {
        for (Map.Entry<String, Object> entry : instanceMap.entrySet()) {
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {
                if(field.isAnnotationPresent(Qualifier.class)){
                    String name = field.getAnnotation(Qualifier.class).value();
                    field.setAccessible(true);
                    field.set(entry.getValue(),instanceMap.get(name));
                }
            }
        }
    }

    private void handlerUrlMethodMap() throws ClassNotFoundException {
        if (packageNames.size()<1){
            return;
        }
        for (String packageName : packageNames) {
            Class<?> c = Class.forName(packageName);
            if (c.isAnnotationPresent(Controller.class)){
                Method[] methods = c.getMethods();
                StringBuffer baseUrl = new StringBuffer();
                if (c.isAnnotationPresent(RequestMapping.class)){
                    RequestMapping requestMapping = c.getAnnotation(RequestMapping.class);
                    baseUrl.append(requestMapping.value());
                }
                for (Method method : methods) {
                    if (method.isAnnotationPresent(RequestMapping.class)){
                        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                        baseUrl.append(requestMapping.value());
                        urlMethodMap.put(baseUrl.toString(),method);
                        methodPackageMap.put(method,packageName);
                    }
                }
            }

        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String uri = req.getRequestURI();
        String contextPath = req.getContextPath();
        String path = uri.replaceAll(contextPath, "");
        Method method = urlMethodMap.get(path);
        if (method!=null){
            String packageName = methodPackageMap.get(method);
            String controllerName = nameMap.get(packageName);
            Object o = instanceMap.get(controllerName);
            try {
                method.setAccessible(true);
                method.invoke(o);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }

        }
    }
}
